﻿'This file is part of WSUS Helper <http://wsushelper.codeplex.com/>.

'WSUS Helper is free software: you can redistribute it and/or modify
'it under the terms of the GNU General Public License as published by
'the Free Software Foundation, either version 2 of the License, or
'(at your option) any later version.

'WSUS Helper is distributed in the hope that it will be useful,
'but WITHOUT ANY WARRANTY; without even the implied warranty of
'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'GNU General Public License for more details.

'You should have received a copy of the GNU General Public License
'along with WSUS Helper.  If not, see <http://www.gnu.org/licenses/>.

Imports Microsoft.UpdateServices.Administration
Imports System.Text
Imports WSUSHelper

Public Class Main

    Const sColCompTarget As String = "ICompTarg"
    Const sColName As String = "Name"
    Const sColOS As String = "OS"
    Const sColPlatform As String = "Platform"
    Const sColSync As String = "Sync"
    Const sColStatus As String = "Status"
    Const sColADDesc As String = "ADDesc"

    Private NewRowArray(8) As String

    Private ComplianceUpdates As UpdateCollection

    Private WithEvents oWSUS As WSUSWrapper
    Private ShowHideTip As ToolTip

    Public sbLog As StringBuilder

    ''' <summary>
    ''' Adds a timestamped entry to the log.
    ''' </summary>
    ''' <param name="Entry">Text to add to the log.</param>
    ''' <remarks></remarks>
    Public Sub LogThis(ByVal Entry As String) Handles oWSUS.OnLogMessage
        tssLog.Text = "[" & Now.ToLongTimeString & "] " & Entry
        sbLog.AppendLine(tssLog.Text)
        Application.DoEvents()
    End Sub

    ''' <summary>
    ''' Reset the progress bar and start tracking with the specified maximum value.
    ''' </summary>
    ''' <param name="iValue">Maximum value of progress to be tracked.</param>
    ''' <remarks></remarks>
    Private Sub StartProgress(ByVal iValue As Integer) Handles oWSUS.OnProgressStart
        'Set up progress bar
        tspOp.Value = 0
        tspOp.Maximum = iValue
        tssStop.Visible = True

        'Disable action buttons and menus
        btnCompMenu.Enabled = False
        btnGroupMenu.Enabled = False
        cmComputer.Enabled = False
        btnSearchUpd.Enabled = False
        btnCheckUpd.Enabled = False

        Application.DoEvents()

    End Sub

    ''' <summary>
    ''' Increment the progress bar by a single step.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub PerformStep() Handles oWSUS.OnProgressPerformStep
        tspOp.PerformStep()
        Application.DoEvents()
    End Sub

    ''' <summary>
    ''' Resets the progress bar and stops tracking.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub EndProgress() Handles oWSUS.OnProgressFinish
        'Reset the progress bar
        tspOp.Value = 0
        tssStop.Visible = False

        'Enable action buttons and menus
        btnCompMenu.Enabled = True
        btnGroupMenu.Enabled = True
        cmComputer.Enabled = True
        btnSearchUpd.Enabled = True
        btnCheckUpd.Enabled = True

        Application.DoEvents()
    End Sub

    ''' <summary>
    ''' Check whether the WSUS object is ready to connect, and enable/disable controls as needed
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub CheckConnectionDetails()
        btnRefreshGroups.Enabled = oWSUS.ReadyToConnect
        btnSearchUpd.Enabled = oWSUS.ReadyToConnect
    End Sub

    ''' <summary>
    ''' Summarises a single computer table row into string form.
    ''' </summary>
    ''' <param name="Row">Row object containing computer details.</param>
    ''' <returns>Summary of computer info.</returns>
    ''' <remarks></remarks>
    Private Function GetInfoText(ByRef Row As DataGridViewRow) As String
        Dim sbInfo As New StringBuilder

        'Retrieve computer object from table
        Dim oComp As IComputerTarget
        oComp = Row.Cells(sColCompTarget).Value

        With sbInfo
            .Append(Row.Cells(sColOS).Value)

            'Add these details only if the computer has actually reported in to WSUS
            If Not oComp Is Nothing Then
                .AppendLine(" " & oComp.OSArchitecture)
                If oComp.OSInfo.Version.ServicePackMajor > 0 Then
                    .AppendLine("Service Pack" & oComp.OSInfo.Version.ServicePackMajor)
                End If
                .AppendLine()
                .AppendLine(oComp.Make)
                .AppendLine(oComp.Model)
                .AppendLine("BIOS: " & oComp.BiosInfo.Version)
                .AppendLine()
                .AppendLine("Last Sync: " & oComp.LastSyncTime)
                .AppendLine("From IP: " & oComp.IPAddress.ToString)
            Else
                'Otherwise inform the user that this computer has not reported in.
                .AppendLine()
                .AppendLine()
                .AppendLine("This computer has not reported in to WSUS.")
            End If

            .AppendLine()
            .AppendLine(Row.Cells(sColADDesc).Value)

        End With

        Return sbInfo.ToString

    End Function

    ''' <summary>
    ''' Removes the computers selected in the table from WSUS.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub RemoveFromWSUS()

        Dim oRemoveComputers As ComputerTargetCollection = GroupGridSelectedList()

        'Inform the user if none of the selection can be removed
        If oRemoveComputers.Count = 0 Then
            MessageBox.Show("None of the selected computers have reported in to WSUS, so cannot be removed.", "Cannot remove from WSUS", MessageBoxButtons.OK)
        Else
            'Set up a warning to the user about the machines they are about to remove

            Dim oMessage As New StringBuilder

            oMessage.AppendLine("You are about to remove the following " & IIf(oRemoveComputers.Count > 1, "machines", "machine") & " from WSUS:")
            oMessage.AppendLine()

            'Build a list of machines to be removed
            For Each oComp As IComputerTarget In oRemoveComputers
                oMessage.AppendLine(oComp.FullDomainName)
            Next
            oMessage.AppendLine()

            oMessage.AppendLine("This cannot be undone. Are you sure you wish to proceed?")

            'Confirm with user all machines they are about to remove, then remove them.
            If MessageBox.Show(oMessage.ToString, "Confirm removal from WSUS", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) = Windows.Forms.DialogResult.Yes Then

                'Remove all selected computers from WSUS
                Dim oRemoved As ComputerTargetCollection = oWSUS.RemoveFromWSUS(oRemoveComputers)

                If Not oRemoved Is Nothing Then

                    'If computers were successfully removed, remove them from the grid
                    If oRemoved.Count > 0 Then

                        Dim oComp As IComputerTarget
                        For Each oRow As DataGridViewRow In dgComputersInGroup.SelectedRows
                            oComp = oRow.Cells(sColCompTarget).Value

                            'Remove this computer from the grid
                            If oRemoved.Contains(oComp) Then dgComputersInGroup.Rows.Remove(oRow)

                        Next

                        LogThis("Successfully removed " & oRemoved.Count & "/" & oRemoveComputers.Count & IIf(oRemoveComputers.Count > 1, " computers", " computer") & " from WSUS.")
                    End If

                End If

            End If

        End If

    End Sub

    ''' <summary>
    ''' Collection of all the computers that have details in the Group Management grid.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GroupGridAllMemberList() As ComputerTargetCollection
        Dim oResult As New ComputerTargetCollection

        'Add all the computer targets in the grid to the list
        For Each oRow As DataGridViewRow In dgComputersInGroup.Rows
            oResult.Add(oRow.Cells(sColCompTarget).Value)
        Next

        Return oResult

    End Function

    ''' <summary>
    ''' Collection of all the computers selected in the Group Management grid.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GroupGridSelectedList() As ComputerTargetCollection
        Dim oResult As New ComputerTargetCollection

        'Add all the valid computer targets in the grid to the list
        For Each oRow As DataGridViewRow In dgComputersInGroup.SelectedRows
            If Not oRow.Cells(sColCompTarget).Value Is Nothing Then oResult.Add(oRow.Cells(sColCompTarget).Value)
        Next

        Return oResult

    End Function


    Private Sub CheckAD()

        'Get details for these computers
        Dim oDetails As Dictionary(Of IComputerTarget, WSUSWrapper.ComputerAccountDetail) = oWSUS.CheckAgainstAD(GroupGridAllMemberList)

        If Not oDetails Is Nothing Then

            Dim oComputer As IComputerTarget
            'Add retrieved details to the grid
            For Each oRow As DataGridViewRow In dgComputersInGroup.Rows
                oComputer = oRow.Cells(sColCompTarget).Value

                If oDetails.ContainsKey(oComputer) Then
                    oRow.Cells(sColADDesc).Value = oDetails(oComputer).Description
                    oRow.Cells(sColStatus).Value = oDetails(oComputer).Status
                End If
            Next
        End If

        'If we are checking the All Computer group, also find computers that have not checked in
        Dim oGroup As IComputerTargetGroup = cbViewGroup.SelectedItem
        If oGroup.Id = ComputerTargetGroupId.AllComputers Then
            Dim oUnreported As List(Of WSUSWrapper.ComputerAccountDetail) = oWSUS.GetUnreportedComputersFromAD(oDetails)
            Dim oRow As DataGridViewRow

            'Sort the unreported computers by name
            oUnreported.Sort(New WSUSWrapper.ComputerAccountDetailSort)

            'Add each unreported machine to the grid
            For Each oAccount As WSUSWrapper.ComputerAccountDetail In oUnreported
                'Add new blank row and get reference to it
                oRow = dgComputersInGroup.Rows(dgComputersInGroup.Rows.Add(NewRowArray))
                oRow.Cells(sColName).Value = oAccount.ComputerFQDN
                oRow.Cells(sColADDesc).Value = oAccount.Description
                oRow.Cells(sColOS).Value = oAccount.OS
                oRow.Cells(sColPlatform).Value = "(Unknown)"
                oRow.Cells(sColStatus).Value = oAccount.Status
                oRow.Cells(sColSync).Value = "Never"
            Next

        End If


    End Sub

    ''' <summary>
    ''' Update the UI based on the current selection in the Group Management grid.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub UpdateGroupManagementSelection()

        'If at least one computer is selected, display text summary in right pane
        If dgComputersInGroup.SelectedRows.Count > 0 Then

            'If a single computer is selected, display full summary
            If dgComputersInGroup.SelectedRows.Count = 1 Then
                tssSelected.Text = "1 item selected."
                lblCompName.Text = dgComputersInGroup.SelectedRows(0).Cells("Name").Value
                lblCompInfo.Text = GetInfoText(dgComputersInGroup.SelectedRows(0))
                lblCompInfo.Visible = True
            Else
                'If multiple computers selected, display how many and list their names
                tssSelected.Text = dgComputersInGroup.SelectedRows.Count & " items selected."
                lblCompInfo.Visible = False
                lblCompName.Text = ""

                Dim iScan As Integer = 0

                While iScan < dgComputersInGroup.SelectedRows.Count
                    lblCompName.Text += dgComputersInGroup.SelectedRows(iScan).Cells("Name").Value & vbCrLf
                    iScan += 1
                End While

            End If

            'Enable the computer actions
            btnCompMenu.Enabled = True

        Else
            'No computers selected, clear right pane and disable the remove button
            lblCompName.Text = ""
            lblCompInfo.Text = ""
            tssSelected.Text = ""
            btnCompMenu.Enabled = False
        End If
    End Sub

    Private Sub dgComputers_SelectionChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles dgComputersInGroup.SelectionChanged
        UpdateGroupManagementSelection()
    End Sub

    Private Sub Main_Shown(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Shown
        LogThis("Ready.")
    End Sub

    Private Sub Main_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

        'Set this here because of a framework bug that breaks the form if this is done in the Designer.
        scMain.Panel2MinSize = 228

        'Create a tooltip for the button because it's not there by default
        ShowHideTip = New ToolTip(Me.components)
        ShowHideTip.SetToolTip(btnShowHidePanel, "Hide the info/action panel")

        'CReate the log storage
        If sbLog Is Nothing Then sbLog = New StringBuilder

        'Set the font for the UI to be the default system font, regardless of what was set in the designer
        Me.Font = System.Drawing.SystemFonts.DialogFont

        'Load the default LDAP query string.
        Dim oDomain As New DirectoryServices.DirectoryEntry()
        oDomain.Path = "LDAP://RootDSE"

        Try
            txtDomRoot.Text = oDomain.Properties("dn").Value
        Catch ex As Exception
            txtDomRoot.Text = ""
        End Try


        'Try to autodetect the WSUS server
        oWSUS = New WSUSWrapper(True)

        'Set the 
        txtWSUS.Text = oWSUS.HostName
        cbPort.Text = oWSUS.PortNumber

        Try
            txtDomRoot.Text = oDomain.Properties("defaultNamingContext").Item(0)
        Catch ex As Exception
            txtDomRoot.Text = ""
        End Try

    End Sub

    Private Sub tssLog_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tssLog.Click
        Log.ShowDialog()
    End Sub


    Private Sub AcceptOnlyNumerals(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles cbPort.KeyPress
        Select Case e.KeyChar
            Case "0" To "9", Chr(8)
                'Accept numerals and Backspace
            Case Else
                e.KeyChar = ""
        End Select
    End Sub

    Private Sub cbPort_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cbPort.TextChanged

        'Check or uncheck the SSL tickbox if the entered port is a well-known WSUS port
        If (cbPort.Text = "443" Or cbPort.Text = "8531") Then
            cbSSL.Checked = True
        ElseIf (cbPort.Text = "80" Or cbPort.Text = "8530") Then
            cbSSL.Checked = False
        End If

        If Not oWSUS Is Nothing Then
            oWSUS.PortNumber = cbPort.Text
        End If

    End Sub

    Private Sub SearchUpdates()

        LogThis("Searching for updates matching """ & txtSearchFor.Text & """")
        Me.Cursor = Cursors.WaitCursor
        Application.DoEvents()

        Try
            'Get matching updates from WSUS
            If Not ComplianceUpdates Is Nothing Then ComplianceUpdates.Clear()
            ComplianceUpdates = oWSUS.SearchUpdates(txtSearchFor.Text, Not chkNoSuper.Checked)

            'If at least 1 update found
            If ComplianceUpdates.Count > 0 Then

                'Clear existing columns
                ComplianceGrid.Columns.Clear()
                ComplianceGrid.Rows.Clear()

                ComplianceGrid.Columns.Add("CompName", "Computer")
                ComplianceGrid.Columns.Add("Compliance", "Compliance")

                For Each oUpdate As IUpdate In ComplianceUpdates
                    ComplianceGrid.Columns.Add(oUpdate.Id.ToString, oUpdate.Title)
                Next

                LogThis("Found " & IIf(ComplianceUpdates.Count = 1, "1 matching update.", ComplianceUpdates.Count & " matching updates."))
            End If

        Catch ex As Exception
            LogThis("Could not find updates: " & ex.Message)
        End Try

        Me.Cursor = Cursors.Default
        btnCheckUpd.Enabled = (cbViewGroup.Items.Count > 0) And (ComplianceGrid.Columns.Count > 0)

    End Sub

    Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearchUpd.Click
        SearchUpdates()
    End Sub

    Private Sub txtSearchFor_KeyPress(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles txtSearchFor.KeyPress

        'If user presses enter, activate the search button.
        If e.KeyChar = Chr(13) And btnSearchUpd.Enabled Then
            btnSearchUpd.Focus()
            SearchUpdates()
        End If
    End Sub

    Private Sub ClearUpdateCompliance()
        For iScan = ComplianceGrid.Rows.Count - 1 To 1 Step -1
            ComplianceGrid.Rows.RemoveAt(iScan)
        Next
    End Sub

    ''' <summary>
    ''' Create a new empty row for the compliance grid
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function NewComplianceRow() As DataGridViewRow

        Dim oNewRow As New DataGridViewRow

        'Only populate the row of there are updates to check
        If ComplianceGrid.Columns.Count > 1 Then
            For iCell As Integer = 0 To ComplianceGrid.Columns.Count - 1
                oNewRow.Cells.Add(New DataGridViewTextBoxCell)
            Next

            Dim oBoldFont As New Font(ComplianceGrid.DefaultCellStyle.Font, FontStyle.Bold)
            oNewRow.Cells(1).Style.Font = oBoldFont

        End If

        Return oNewRow

    End Function


    Private Sub CheckUpdateCompliance()

        Me.Cursor = Cursors.WaitCursor
        Application.DoEvents()

        'Store how many updates we have to check
        Dim iUpdCount As Integer = ComplianceGrid.Columns.Count - 2

        'Only do something if there are updates to check
        If iUpdCount > 0 Then

            Dim oCheckComps As IComputerTargetGroup = cbViewGroup.SelectedItem

            'Clear the compliance grid.
            ClearUpdateCompliance()

            Dim oCompliance As New Dictionary(Of String, WSUSWrapper.UpdateCompliance)
            'Initialise compliance tracking for each computer
            For Each oComp As IComputerTarget In oCheckComps.GetComputerTargets

                'oUpdateMatrix.Add(oComp.Id, oNewRow)
                oCompliance.Add(oComp.Id, New WSUSWrapper.UpdateCompliance)
            Next

            Dim iUpdScan As Integer = 2

            'Attempt to get compliance and display results if successful
            If oWSUS.GetUpdateCompliance(ComplianceUpdates, oCheckComps, oCompliance) Then
                Dim oNewRow As DataGridViewRow

                'Enumerate computers that have now been checked
                Dim oTargets As ComputerTargetCollection = oCheckComps.GetComputerTargets

                For Each oComp As IComputerTarget In oTargets

                    'Create a new row for the grid
                    oNewRow = NewComplianceRow()

                    'Put computer name in the new row
                    oNewRow.Cells(0).Value = oComp.FullDomainName

                    'Retrieve compliance for each update and write it to the new row
                    For iCell As Integer = 2 To iUpdCount + 1
                        oNewRow.Cells(iCell).Value = oCompliance(oComp.Id).ItemStateDescription(ComplianceUpdates(iCell - 2))
                    Next

                    'Get the worst state found for the updates that were checked and write this to the new row
                    oNewRow.Cells(1).Value = oCompliance(oComp.Id).WorstStateDescription

                    'Add the row to the grid.
                    ComplianceGrid.Rows.Add(oNewRow)

                Next

                LogThis("Checked compliance of " & IIf(oTargets.Count = 1, "1 computer.", oTargets.Count & " computers."))

                'Enable export if some computers were checked.
                btnCompToCSV.Enabled = (oTargets.Count > 0)

            End If

        End If

        Me.Cursor = Cursors.Default

    End Sub

    Private Sub btnCheckUpd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCheckUpd.Click
        'Check update complicance
        CheckUpdateCompliance()
    End Sub

    Private Sub ReuseFileName()
        Dim sSplit As String() = sfToCSV.FileName.Split("\")
        If sSplit.Length > 0 Then
            sfToCSV.FileName = sSplit(sSplit.Length - 1)
        End If
    End Sub

    Private Sub btnExportCompliance_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCompToCSV.Click

        'Set the last used file name if there is one
        ReuseFileName()

        'Launch the save as dialog, and if a filename was selected, save the compliance info table
        If sfToCSV.ShowDialog = Windows.Forms.DialogResult.OK Then
            Dim sResult As String = ""

            If Not FileOutput.SaveTableToCSV(ComplianceGrid, sfToCSV.FileName, sResult) Then
                'Display a message box if the save was unsuccessful
                MessageBox.Show(sResult, "Unable to save compliance data", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End If
            'Log the result
            LogThis(sResult)
        End If

    End Sub


    ''' <summary>
    ''' Refresh the list of groups from the currently specified WSUS server.
    ''' </summary>
    ''' <param name="cbGroupList"></param>
    ''' <remarks></remarks>
    Private Sub RefreshGroups(ByVal cbGroupList As ComboBox)

        Me.Cursor = Cursors.WaitCursor
        Application.DoEvents()

        Try
            'Attempt to get the list of groups from WSUS
            Dim oGroups As ComputerTargetGroupCollection = oWSUS.Server.GetComputerTargetGroups()

            'Clear the computer groups dropdown and repopulate
            cbViewGroup.Items.Clear()
            cbViewGroup.ValueMember = "Name"

            'Add each group to the list
            For Each oGroup As IComputerTargetGroup In oGroups
                cbViewGroup.Items.Add(oGroup)
                'When we find the 'All Computers' group, select it
                If oGroup.Id = ComputerTargetGroupId.AllComputers Then
                    cbViewGroup.SelectedItem = oGroup
                End If
            Next

        Catch ex As Exception
            LogThis("Could not refresh groups: " & ex.Message)
        End Try

        'Enable controls if at least one group was loaded
        cbViewGroup.Enabled = (cbViewGroup.Items.Count > 0)
        btnCheckUpd.Enabled = (cbViewGroup.Items.Count > 0) And (ComplianceGrid.Columns.Count > 0)

        Me.Cursor = Cursors.Default

    End Sub

    Private Sub cbViewGroup_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cbViewGroup.SelectedIndexChanged
        If TabMain.SelectedIndex = 0 Then
            RefreshCurrentGroupMembers()
        End If
    End Sub

    ''' <summary>
    ''' Empty the Group Management grid and set up the columns ready to be repopulated.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub SetupGroupManagementGrid()

        'Clear the grid entirely.
        dgComputersInGroup.Columns.Clear()

        'Set up the table columns
        If dgComputersInGroup.Columns.Count = 0 Then
            With dgComputersInGroup
                .Columns(.Columns.Add(sColCompTarget, "Target")).Visible = False
                .Columns(.Columns.Add("Name", "Name")).Resizable = True
                .Columns(.Columns.Add("OS", "OS")).Resizable = True
                .Columns(.Columns.Add("Platform", "Platform")).Resizable = True
                .Columns(.Columns.Add("Sync", "Last Sync")).Resizable = True
                .Columns(.Columns.Add("Status", "Status")).Resizable = True
                .Columns(.Columns.Add("ADDesc", "Description")).Resizable = True
            End With
        End If
        NewRowArray = {Nothing, "", "", "", "", "", ""}

    End Sub


    ''' <summary>
    ''' Refresh the Group Management grid based on the group currently selected in the UI.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub RefreshCurrentGroupMembers()

        'Reset the grid
        SetupGroupManagementGrid()

        Dim oSelectedGroup As IComputerTargetGroup = cbViewGroup.SelectedItem

        LogThis("Scanning WSUS for computers in group '" & oSelectedGroup.Name & "'...")

        'Cannot remove computers from the All Computers group
        miRemoveFromGroup.Enabled = Not (oSelectedGroup.Id = ComputerTargetGroupId.AllComputers)

        Try
            'Get a list of the computers in the selected group
            Dim oList As List(Of IComputerTarget) = oWSUS.SortedComputerTargets(oSelectedGroup.GetComputerTargets)

            StartProgress(oList.Count)

            Dim iNewRow As Integer
            'Add each row to the grid
            For Each oItem In oList
                iNewRow = dgComputersInGroup.Rows.Add({oItem, oItem.FullDomainName, oItem.OSDescription, oItem.OSArchitecture, oItem.LastSyncTime.ToString, "", ""})
                dgComputersInGroup.Rows(iNewRow).ContextMenuStrip = cmComputer
                PerformStep()
            Next

            LogThis("Found " & IIf(oList.Count = 1, "1 computer.", oList.Count & " computers."))
            EndProgress()

        Catch ex As Exception
            LogThis("Could not get list of computers in group '" & oSelectedGroup.Name & "': " & ex.Message)
        End Try

        'Enable group actions if at least one group was found.
        btnGroupMenu.Enabled = (cbViewGroup.Items.Count > 0)

        UpdateGroupManagementSelection()

    End Sub

    ''' <summary>
    ''' Remove the computers selected in the grid from the current group.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub RemoveSelectedFromGroup()

        Dim oSelectedGroup As IComputerTargetGroup = cbViewGroup.SelectedItem

        'Confirm removal with user
        If MessageBox.Show("Remove " & dgComputersInGroup.SelectedRows.Count & IIf(dgComputersInGroup.SelectedRows.Count = 1, " computer", " computers") _
                           & " from this group?", "Confirm removal", MessageBoxButtons.OKCancel) = Windows.Forms.DialogResult.OK Then

            'Remove the selected coputers from this group
            oWSUS.RemoveFromGroup(GroupGridSelectedList, oSelectedGroup)

            'Refresh the group membership list.
            RefreshCurrentGroupMembers()

        End If
    End Sub

    ''' <summary>
    ''' Adds the selected computers in the grid to a group of the user's choosing.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub AddToGroup()

        Dim oAddComputers As ComputerTargetCollection = GroupGridSelectedList()

        If oAddComputers.Count = 0 Then
            MessageBox.Show("None of the selected computers have reported in to WSUS, so cannot be added to a group.", "Cannot add to group", MessageBoxButtons.OK)
        Else

            'Choose which group to add the computers to.
            Dim oResult As DialogResult = GroupPicker.ShowDialog()

            'If the user picked a group (did not click Cancel)
            If oResult = DialogResult.OK Then

                'Add the selected computers to the group.
                oWSUS.AddToGroup(oAddComputers, GroupPicker.SelectedGroup)

            End If
        End If

    End Sub

    Private Sub CopyApprovals()

        Dim oSourceGroup As IComputerTargetGroup = cbViewGroup.SelectedItem

        'Get the user to pick a group to copy the approvals to
        Dim oResult As DialogResult = GroupPicker.ShowDialog()

        'If they picked a group (didn't click cancel)
        If oResult = DialogResult.OK Then

            'Retrieve the target group and copy approvals
            oWSUS.CopyApprovals(oSourceGroup, GroupPicker.SelectedGroup)

        End If

    End Sub

    Private Sub btnRefreshGroups_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefreshGroups.Click
        'Load groups
        RefreshGroups(cbViewGroup)
    End Sub

    Private Sub txtWSUS_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtWSUS.TextChanged
        oWSUS.HostName = txtWSUS.Text.Trim
    End Sub

    Private Sub txtDomRoot_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtDomRoot.TextChanged
        oWSUS.DirectoryRoot = txtDomRoot.Text.Trim

        miCheckAD.Enabled = (oWSUS.DirectoryRoot.Length > 0)
    End Sub

    Private Sub btnCompMenu_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCompMenu.Click
        cmComputer.Show(sender, 0, sender.Height)
    End Sub

    Private Sub btnGroupMenu_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnGroupMenu.Click
        cmGroup.Show(sender, 0, sender.Height)
    End Sub

    Private Sub Main_KeyUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyUp
        'If the user presses the escape key, abort any ongoing action.
        If e.KeyCode = Keys.Escape Then
            oWSUS.StopOperation()
            tssStop.Visible = False
        End If
    End Sub

    Private Sub miCheckAD_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles miCheckAD.Click
        CheckAD()
    End Sub

    Private Sub miCopyApprovals_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles miCopyApprovals.Click
        CopyApprovals()
    End Sub

    Private Sub miRemoveFromAD_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles miRemoveFromAD.Click
        RemoveFromWSUS()
    End Sub

    Private Sub btnShowHidePanel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnShowHidePanel.Click
        'Toggle visibility of the action panel
        scMain.Panel2Collapsed = Not scMain.Panel2Collapsed

        'Set the tooltip of the show/hide button
        If scMain.Panel2Collapsed Then
            ShowHideTip.SetToolTip(btnShowHidePanel, "Show the info/action panel")
        Else
            ShowHideTip.SetToolTip(btnShowHidePanel, "Hide the info/action panel")
        End If

    End Sub

    Private Sub dgComputersInGroup_CellMouseUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellMouseEventArgs) Handles dgComputersInGroup.CellMouseUp
        'If the user right-clicks on a non-selected row, change the selection to that row.
        If e.Button = Windows.Forms.MouseButtons.Right Then
            If Not dgComputersInGroup.Rows(e.RowIndex).Selected Then
                dgComputersInGroup.ClearSelection()
                dgComputersInGroup.Rows(e.RowIndex).Selected = True
            End If
        End If
    End Sub

    Private Sub TabMain_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TabMain.SelectedIndexChanged
        'Only show the show/hide action panel button if we are on the Group Management tab
        btnShowHidePanel.Visible = (TabMain.SelectedIndex = 0)

        If TabMain.SelectedIndex = 0 Then
            UpdateGroupManagementSelection()
            RefreshCurrentGroupMembers()
        Else
            tssSelected.Text = ""
            txtSearchFor.Focus()
        End If
    End Sub

    Private Sub miExport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles miExport.Click

        'Set the last used file name if there is one
        ReuseFileName()

        'Launch the save as dialog, and if a filename was selected, save the AD check table
        If sfToCSV.ShowDialog = Windows.Forms.DialogResult.OK Then
            Dim sResult As String = ""

            If Not FileOutput.SaveTableToCSV(dgComputersInGroup, sfToCSV.FileName, sResult) Then
                'Display a message box if the save was unsuccessful
                MessageBox.Show(sResult, "Unable to save computer data", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End If

            'Log the result
            LogThis(sResult)
        End If

    End Sub

    Private Sub miAddToGroup_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles miAddToGroup.Click
        AddToGroup()
    End Sub

    Private Sub miRemoveFromGroup_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles miRemoveFromGroup.Click
        RemoveSelectedFromGroup()
    End Sub

End Class
